package com.ruoyi.aitu9c.service;

import java.io.File;
import java.util.*;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ruoyi.aitcommon.config.AITConfig;
import com.ruoyi.aitcommon.utils.*;
import com.ruoyi.aitcommon.vo.AggMap;
import com.ruoyi.aitcommon.vo.AttachmentVO;
import com.ruoyi.common.core.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.aitcommon.anotation.ServiceLog;
import com.ruoyi.aitcommon.service.IAitEntityInfoService;
import com.ruoyi.aitu9c.utils.U9JSONResultUtils;
import com.ruoyi.aitu9c.vo.U9ContextVO;
import com.ruoyi.aitu9c.vo.U9ParmVO;
import com.ruoyi.common.constant.HttpStatus;

@Service
public class U9RestService {
    @Autowired
    IAitEntityInfoService infoService;
    @Autowired
    EntityCache entityCache;
    @Autowired
    RedisCache redisCache;

    /**
     * 将MapList转化为U9格式Map
     * 调用ListMapUtils方法
     * @param values
     * @param fengge
     * @param zuhe
     * @return
     * @throws Exception
     */
    @ServiceLog("MAP转换U9实体服务")
    public List<Map<String, Object>> transU9Map(List<Map<String, Object>> values
            , List<Map<String, Object>> fengge, String zuhe) throws Exception {
        return ListMapUtils.transU9Map(values, fengge, zuhe);
    }

    @ServiceLog("u9 rest调用")
    public JSONArray restInvoke(Map<String, Object> context, String url, Object data) throws Exception {
        if (data == null) {
            return null;
        }
        // 转换context为VO
        JSONObject contextJson = new JSONObject(context);
        U9ContextVO contextVO = JSONObject.toJavaObject(contextJson, U9ContextVO.class);
        // 装配ncc json
        U9ParmVO parmvo = new U9ParmVO();
        parmvo.context = contextVO;
        parmvo.data = data;
        // 调用u9服务
//		JSONObject.toJSONStringWithDateFormat方法有BUG,map层数三层时,string翻译出错
//		所以重新改为下述方式,直接toJSONString
//		DisableCircularReferenceDetect https://blog.csdn.net/qq_42449963/article/details/122720398
        String retstr = RestServiceUtils.post(url
                , JSONObject.toJSONString(parmvo, SerializerFeature.DisableCircularReferenceDetect));
        // 处理u9服务返回
        if (retstr.startsWith("<!DOCTYPE html>"))
            throw new Exception("U9正在加载,请重新调用");
        JSONObject retjson = JSONObject.parseObject(retstr);
        JSONObject retjson1 = U9JSONResultUtils.toResult(retjson);
        JSONResult.jsonAssert(retjson1);
        return U9JSONResultUtils.getDataArray(retjson1);

    }

    /**
     * U9实体更新服务
     * 调用restInvoke方法进行远程调用
     * @param context 接口上下文
     * @param url     接口地址
     * @param parm    接口参数
     * @param datas   接口数据
     * @return
     * @throws Exception
     */
    @ServiceLog("u9 实体更新")
    public JSONArray mapUpdate(Map<String, Object> context, String url
            , Map<String, Object> parm, List<Object> datas) throws Exception {
        ExceptionUtils.checkBlankOrUndefined(context, "context为空");
        ExceptionUtils.checkBlankOrUndefined(url, "url为空");
        ExceptionUtils.checkBlankOrUndefined(parm, "parm");

        if (ObjUtils.isBlank(datas)) return null;
        final String datastr = JSONObject.toJSONString(datas);
        parm.put("Datas", datastr);
        final JSONArray retarr = this.restInvoke(context, url, parm);
        return retarr;
    }

    /**
     * U9单据/档案保存服务
     *
     * @param context    调用U9上下文,支持${}变量
     * @param url        调用U9链接
     * @param datas      传入数据
     * @param entityInfo 传入参数的entityInfo配置
     * @return
     * @throws Exception
     */
    @ServiceLog("u9 档案保存")
    public List<JSONObject> entitySave(Map<String, Object> context, String url
            , List<Map<String, Object>> datas, Map<String, Object> entityInfo) throws Exception {
        return this.entitySaveEx(context, url, datas, entityInfo, null);
    }

    /**
     * U9实体保存并记录成功返回信息
     *
     * @param context     调用U9上下文,支持${}变量,,从datas中取数,构造动态context
     * @param url         调用U9链接
     * @param datas       传入数据
     * @param entityInfo  传入参数的entityInfo配置
     * @param successInfo 成功返回信息的对应entityInfo配置
     * @return
     * @throws Exception
     */
    @ServiceLog("u9 档案保存")
    public List<JSONObject> entitySaveEx(Map<String, Object> context, String url
            , List<Map<String, Object>> datas, Map<String, Object> entityInfo, Map<String, Object> successInfo) throws Exception {
        List<JSONObject> retlist = new ArrayList<JSONObject>();

        ExceptionUtils.checkBlankOrUndefined(context, "context为空");
        ExceptionUtils.checkBlankOrUndefined(url, "url为空");
        ExceptionUtils.checkBlankOrUndefined(entityInfo, "entityInfo为空");

        // 从缓存过滤重复数据
        List<Map<String, Object>> filterlist = entityCache.filterEntityChange(entityInfo, datas);

        // 针对每行数据,逐行调用U9服务
        for (Map<String, Object> data : filterlist) {
            try {
                ExceptionUtils.checkBlankOrUndefined(url, "数据:{},未配置url地址", data.toString());
//				依据data动态构造context
                Map<String, Object> context1 = FlowParamUtils.getFlowParamsValue(context, data);
                //远程调用
                JSONArray retdatas = restInvoke(new JSONObject(context1), url, Arrays.asList(data));
                //数据日志处理
                if (entityInfo != null) {
                    if (!entityInfo.containsKey("oper"))
                        entityInfo.put("oper", "保存");
//					将服务返回值放入传入参数data
                    if (retdatas != null && retdatas.size() > 0 && successInfo != null)
                        infoService.insertAitEntityInfo(entityInfo, data, successInfo, retdatas.getJSONObject(0), HttpStatus.SUCCESS, null);
                    else
                        infoService.insertAitEntityInfo(entityInfo, data, null);
                }
//				处理服务的返回值
                if (retdatas != null && retdatas.size() > 0) {
                    for (Object retdata : retdatas) {
                        if(ObjUtils.isNotBlank(retdata))
                            retlist.add((JSONObject) retdata);
                    }
                }
            } catch (Exception ex) {
//				ex.printStackTrace();
//				处理实体日志和缓存-失败信息
                infoService.insertAitEntityInfo(entityInfo, data, ex);
//				处理返回
                JSONObject retjson = JSONResult.error(ex.getMessage());
                retlist.add(retjson);
            }
        }
        return retlist;
    }

    /**
     * U9单据批量审批
     * 示例: 配置文件示例\demo\flow\oa-u9-batchApprove.json
     *
     * @param datas      需要审批的单据列表,字段 DocNo,status,docname
     * @param context    u9登陆上下文
     * @param urls       审批服务地址清单
     * @param entityInfo
     * @return
     * @throws Exception
     */
    @ServiceLog("u9单据批量审批")
    public List<JSONObject> batchApprove(List<Map<String, Object>> datas
            , Map<String, Object> context, Map<String, Object> urls, Map<String, Object> entityInfo) throws Exception {

        Map<String, Object> datamap = new LinkedHashMap<String, Object>();
        //依据docname分组datas
        for (Map<String, Object> data : datas) {
            String docname = StrUtil.toString(data.get("docname"));
            String status = StrUtil.toString(data.get("status"));
//			key=docname-status,获取具体的url
            String key = docname + "-" + status;

            List<Map<String, Object>> datalist = null;
            if (datamap.containsKey(key)) {
                datalist = (List<Map<String, Object>>) datamap.get(key);
            } else {
                datalist = new ArrayList<Map<String, Object>>();
            }
            datalist.add(data);
            datamap.put(key, datalist);
        }

        //循环docname调用u9接口
        List<JSONObject> retlist = new ArrayList<JSONObject>();
        for (String key : datamap.keySet()) {
            String url = null;
//			依据docname-status,获取url
            if (!urls.containsKey(key)) {
                ExceptionUtils.throwException(null,"获取URL为空,主键:[{}]", key);
            }
            url = StrUtil.toString(urls.get(key));
            List<Map<String, Object>> datalist = (List<Map<String, Object>>) datamap.get(key);
            List<JSONObject> ret = this.entitySave(context, url, datalist, entityInfo);
            retlist.addAll(ret);
        }
        return retlist;
    }

    /**
     * 将U9附件信息转换为H5格式
     * 场景:将附件的H5地址写入U9扩展字段,并传入OA,用于附件点击下载
     * 已作废,建议使用 docAttachment 服务
     * @param datas     u9返回附件信息,需要调用U9的附件查询接口
     * @param codeField u9单据业务主键
     * @param attField  u9单据H5存储字段,存储附件地址H5信息的U9扩展字段
     * @return 结构=[{attField,H5地址}],用于MapSave回写U9
     */
    @ServiceLog("u9单据附件地址更新")
    public List<Map<String, Object>> getAttachmentH5(List<Map<String, Object>> datas, String u9host, String codeField, String attField) {
        Map<String, Object> doclist = new LinkedHashMap<String, Object>();

//		附件信息按照单号分组
        for (Map<String, Object> data : datas) {
            if (data.get("EntityCode") == null) continue;

            String docno = data.get("EntityCode").toString();

            if (doclist.containsKey(docno)) {
                List<Object> attachments = (List<Object>) doclist.get(docno);
                attachments.add(data);
            } else {
                List<Object> attachments = new ArrayList<Object>();
                attachments.add(data);
                doclist.put(docno, attachments);
            }
        }

//		增对每张单构造附件H5
        List<Map<String, Object>> retlist = new ArrayList<Map<String, Object>>();
        for (String key : doclist.keySet()) {
            List<Object> attachments = (List<Object>) doclist.get(key);
            StringBuilder htmls = new StringBuilder();

            String docno = key;
            for (Object obj : attachments) {
                Map<String, Object> attachment = (Map<String, Object>) obj;
                String url = attachment.get("Url").toString();
                String filename = attachment.get("FileName").toString();

                String html = "<p><a href=\"%s\" target=\"_blank\">%s</a></p>";
                html = String.format(html, u9host + url, filename);
                htmls.append(html);
            }
            htmls.append("<p>附件点击打开</p>");

//			构造返回更新datas,[{codeField,htmls}]
            Map<String, Object> retmap = new LinkedHashMap<String, Object>();
            retmap.put(codeField, docno);
            retmap.put(attField, htmls.toString());
            retlist.add(retmap);
        }

        return retlist;
    }

    /**
     * U9附件处理流程,思路如下:
     * 调用U9附件保存接口
     * 将U9附件存入本地
     * 将U9附件信息存储redis
     *
     * @param context
     * @param url
     * @param datas
     * @param entityInfo
     * @param parms
     * @return
     * @throws Exception
     */
    @ServiceLog("u9单据附件处理")
    public List<AggMap> docAttachment(JSONObject context, String url, List<AggMap> datas
            , JSONObject entityInfo, Map<String, Object> parms) throws Exception {
        JSONObject parmsjson = new JSONObject(parms);
        final String entityName = parmsjson.getString("entityName");
        ObjUtils.checkBlank(entityName, "parms中必须包含entityName");
        final String codeField = parmsjson.getString("codeField");
        ObjUtils.checkBlank(codeField, "parms中必须包含codeField");
        final String fileUrl = parmsjson.getString("fileUrl");
        ObjUtils.checkBlank(fileUrl, "parms中必须包含fileUrl");
        final String redisKey = parmsjson.getString("redisKey");
        ObjUtils.checkBlank(fileUrl, "parms中必须包含redisKey");

        for (AggMap data : datas) {
            //循环每条数据,调用NC附件保存接口
            final Object code = data.getHeadVO().get(codeField);
            ObjUtils.checkBlank(code, "数据中不存在[%s],或值为空", codeField);

            //调用U9保存服务
            Map<String, Object> query = new LinkedHashMap<>();
            query.put("EntityFullName", entityName);
            query.put("EntityCode", code);
            query.put("DB", parmsjson.getString("DB"));
            query.put("SaveDir", parmsjson.getString("SaveDir"));
            query.put("ts", new Date());//强制调用接口,调试用
            final List<JSONObject> attlist = this.entitySave(context, url, ObjUtils.toList(query), entityInfo);

            //返回附件处理
            if (ObjUtils.isNotBlank(attlist)) {
                List<AttachmentVO> atts = new ArrayList<>();
                for (Object attobj : attlist) {
                    JSONObject attjson = (JSONObject) attobj;
                    AttachmentVO attvo = new AttachmentVO();
                    attvo.setDocName(entityName);
                    attvo.setDocNo(attjson.getString("EntityCode"));
                    attvo.setFileUrl(fileUrl +"/"+ attjson.getString("Url"));
                    attvo.setFileName(attjson.getString("FileName"));
                    //下载附件,存储在本地
                    String path = AITConfig.getUploadPath() + "\\" + attvo.getDocName() + "\\" + attvo.getDocNo() + "\\" + attvo.getFileName();
                    final File file = RestServiceUtils.getFile(attvo.getFileUrl(), path);
                    attvo.setFilePath(file.getAbsolutePath());

                    atts.add(attvo);
                }
                //将返回数据按照 tablename+docno,存入redis
                String key = redisKey + ":" + entityName + ":" + code;
                redisCache.setCacheObject(key, atts);
            }
        }
        return datas;
    }
}
